-module(chorder). 
-behaviour(gen_server).

-export([start_link/0]).
-export([init/1, handle_call/3,handle_cast/2,handle_info/2
        ,code_change/3,terminate/2]).

% TODO remove
-export([find_responsible_node/2]).

start_link() ->
    io:format("Starting chorder\n"),
    gen_server:start_link({local,?MODULE},?MODULE,[],[]).

%
% Nodes are stored in an ordered set as tuples
% containing {hashed nodename, nodename}
%
init(Nodes) ->
    {ok, ordsets:add_element({hash:hash(node()), node()}
                           , Nodes)}.

% % % % % % % % % % % % % % % % % % % % % % % % % % % % % % %
%
%   Handle incoming calls
%   
%   current interface:
%   
%   {put, Key, Value} -> {ok, put}
%   
%   {get, Key} -> {ok, Value} | error
%
%   {join, Node} -> {ok, join}
%
%   {joinRequest, Node} -> {Nodes, Values}
%
%   {newNode, Node} -> {ok, newNode}
%
%   {leave} -> {ok, leave}
%
%   {leaving} -> {ok, leaving}
%
% % % % % % % % % % % % % % % % % % % % % % % % % % % % % % %

%
% Deliver put and get requests to the responsible node
%
handle_call({put, Key, Value}, _From, Nodes) ->
    RNode = find_responsible_node(Key, Nodes),
    Reply = gen_server:call({storager, RNode}, {put, Key, Value}),
    {reply, Reply, Nodes};

handle_call({get, Key}, _From, Nodes) ->
    RNode = find_responsible_node(Key, Nodes),
    Reply = gen_server:call({storager, RNode}, {get, Key}),
    {reply, Reply, Nodes};

%
% This node want to join a chord network, knows about 'Node' and 
% ask for list with connected nodes and values to be responsible for
%
handle_call({join, Node}, _From, Nodes) ->
    % Check if node is already connected to a network,
    % should be a variable in a state object instead?
    case ordsets:size(Nodes) > 1 of
        true  -> {reply, {error, already_connected}, Nodes}; 
        false ->   
            {NodeList, Values} = gen_server:call({chorder, Node}
                                        ,{joinRequest, node()}),
            % Send new values to storager
            _Reply = gen_server:call(storager, {newValues, Values}),
            io:format("Nodes: ~w\n",[[X || {_,X}<-NodeList]]),
            {reply, {ok, join}, NodeList}
    end;    

%
% Received a join request, some node wants to join this chord network
%
handle_call({joinRequest, Node}, _From, Nodes) ->
    % Find responsible node
    RNode = find_responsible_node(Node, Nodes),
    % Send info about the new node to everyone
    _Replies =[gen_server:call({chorder,X},{newNode, Node})
              || {_,X} <- Nodes, X /= node()],
    % Create new list containing all connected nodes
    NewNodeSet = ordsets:add_element({hash:hash(Node), Node}, Nodes),
    % Divide the values at the responsible node
    NewValues = gen_server:call({storager,RNode}, {newJoin, Node}),
    
    io:format("Nodes: ~w\n",[[X || {_,X}<-NewNodeSet]]),
    {reply, {NewNodeSet, NewValues}, NewNodeSet};


%
% Received info about a new member of the chord network
%
handle_call({newNode, Node}, _From, Nodes) ->
    NewNodes = ordsets:add_element({hash:hash(Node), Node}, Nodes),
    io:format("Nodes: ~w~n",[[X || {_,X} <- NewNodes]]),
    {reply, {ok, newNode}, NewNodes};

%
% This node wants to leave the network
%
handle_call({leave}, _From, Nodes) ->
    io:format("Leaving chord!~n"),
    % Remove itself from nodes list
    NewNodes = ordsets:del_element({hash:hash(node()),node()},Nodes),
    io:format("nodes after del: ~w~n", [[X||{_,X}<-NewNodes]]),
    % Send info about leaving
    _Replies = [gen_server:call({chorder,X},{leaving, node()})
               || {_,X} <- NewNodes],
    % Send all values to the new responsible node
    Values = gen_server:call(storager, {leave}),
    RNode = find_responsible_node(node(),NewNodes),
    io:format("sending values: ~p~n", [Values]),
    _Reply2 = gen_server:call({storager, RNode}, {leavingNode, Values}),
    {reply, {ok, leave}, ordsets:add_element({hash:hash(node()),node()}
                                            ,ordsets:new())};

%
% Catch signal from leaving node
%
handle_call({leaving, Node}, _From, Nodes) ->
    NewNodes = ordsets:del_element({hash:hash(Node),Node},Nodes),
    io:format("Nodes: ~w~n",[[X || {_,X} <- NewNodes]]),
    {reply, {ok, leaving}, NewNodes};

% Return status
handle_call({status}, _From, Nodes) ->
    {reply, Nodes, Nodes}.
%
% Function called when terminated
%
terminate(_Reason,_State) ->
    io:format("Terminating chorder~n").

% Unused functions atm
handle_cast(_Request,_State) ->
    undefined.

handle_info(_Info, _State) ->
    undefined.

code_change(_OldVsn, _State, _Extra) ->
    undefined.
% % % % % % % % % % % % % % % % % % % % % %
%    Helper functions                     %
% % % % % % % % % % % % % % % % % % % % % % 

%
% Given a value (Node or Key), find
% the responsible node

% TODO not working correct?
%
find_responsible_node(Value, Nodes) ->
    NodesList = lists:reverse(ordsets:to_list(Nodes)),
    N = [X || {Hash,X} <- NodesList, hash:hash(Value) =< Hash],
    case length(N) of
        0 -> element(2,lists:nth(1,Nodes));
        _ -> lists:last(N)
    end.


%find_responsible_node2(Value, Nodes) ->
%    List = ordsets:to_list(Nodes),
%    Smaller = lists:takewhile(fun({Hash,X}) -> hash:hash(Value) =< Hash),
    
